/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex *tree = new BTreeIndex;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  bool   index = false;


	// Get information for tree
	int start = 0; // Boundaries to start and end at will INCLUDE these start/end values, if they exist
	int end = -1;
	bool usetree = false;
	bool forcebrk = false;
	IndexCursor ic;
	
	// Do we have an index file?
	if ((rc = tree->open(table + ".idx", 'r')) == 0) { // Choose when to use the index
		count = 0;
		if(attr == 4 || ((cond.size() == 0) && (attr == 1)) ){
			usetree = true;
		}
		for (unsigned i = 0; i < cond.size(); i++) {
			switch (cond[i].attr) { // Check if key
			case 1:
				index = true;
				break;
			default:
				break;
			}
			if(index){
				// Dont use index on key if NE
				switch (cond[i].comp) {
				case SelCond::EQ:
					start = atoi(cond[i].value);
					end = atoi(cond[i].value);
					forcebrk = true;
					break;
				case SelCond::NE:
					index = false;
					break;
				case SelCond::GT:
					if(start <= atoi(cond[i].value)){
						start = atoi(cond[i].value)+1;
					}
					break;
				case SelCond::LT:
					if((end >= atoi(cond[i].value)) || (end == -1)){
						end = atoi(cond[i].value)-1;
					}
					break;
				case SelCond::GE:
					if(start < atoi(cond[i].value)){
						start = atoi(cond[i].value);
					}
					break;
				case SelCond::LE:
					if((end > atoi(cond[i].value)) || (end == -1)){
						end = atoi(cond[i].value);
					}
					break;
				default:
					break;
				}
			}
			if(index){ // If we have even one key condition (NOT NE) we want to use index
				usetree = true;
			}
			if(forcebrk){ // Had condition EQ, don't need any other conditions
				break; // break out of for loop
			}
		}
	}

	//fprintf(stdout, "KEY: %d \n START: %d\n END: %d\n", key, start, end);

	  // open the table file
  if(attr == 2 || attr == 3 || !usetree){ // Only  pf read on SELECT value or SELECT * 
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	} 
  }
	
	
	if(!usetree){
	
	  // scan the table file from the beginning
	  rid.pid = rid.sid = 0;
	  count = 0;
	  while (rid < rf.endRid()) {
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
		  case 1:
		diff = key - atoi(cond[i].value);
		break;
		  case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
		  }

		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
		  }
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		++rid;
	  }
	}else{ // We have an index file, and key condition that isn't NE!
		// Start with correct tuple!
		IndexCursor ic;
		rc = tree->locate(start, ic); // Get the first tuple! This tuple might be > end...check!
		// fprintf(stdout, "ICpage: %d\n ICentry: %d\n RC: %d\n", ic.pid, ic.pid, rc);
		if(rc == 0){ rc = tree->readForward(ic, key, rid); }
		if(rc != 0 || (key > end && end != -1) || (start > end && end != -1)){ // There are no matches
			// print matching tuple count(0) if "select count(*)"
			// fprintf(stdout, "KEY: %d\n START: %d\n END: %d\n", key, start, end);
			if (attr == 4) {
				fprintf(stdout, "%d\n", count);
			}
			rc = 0;
			goto exit_select;
		}
		//fprintf(stdout, "STAR4");
		// We have at least one tuple!
		while(rc == 0){
			//fprintf(stdout, "STAR5");
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) {
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
				}

				// skip the tuple if any condition is not met
				switch (cond[i].comp) {
				case SelCond::EQ:
					if (diff != 0) goto next_tpl;
					break;
				case SelCond::NE:
					if (diff == 0) goto next_tpl;
					break;
				case SelCond::GT:
					if (diff <= 0) goto next_tpl;
					break;
				case SelCond::LT:
					if (diff >= 0) goto next_tpl;
					break;
				case SelCond::GE:
					if (diff < 0) goto next_tpl;
					break;
				case SelCond::LE:
					if (diff > 0) goto next_tpl;
					break;
				}
			}

			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;

			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
			    // Need to do a read
				if ((rc = rf.read(rid, key, value)) < 0) {
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					goto exit_select;
				}
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				// Need to do a read
				if ((rc = rf.read(rid, key, value)) < 0) {
					fprintf(stderr, "Error: while reading a tuple from table %s  %d  \n", table.c_str(), key);
					goto exit_select;
				}
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}

			// move to the next tuple
			next_tpl:
			rc = tree->readForward(ic, key, rid);
			if(rc == 0 && (key > end && end != -1)){ // We have passed the last tuple 
				rc = -1;
			}
		}
	}
	
	
	
	
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  delete tree;
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
/*
	Open the input loadfile and the RecordFile, 
	parse each line of the loadfile to read a tuple (possibly using SqlEngine::parseLoadLine()) 
	and insert the tuple to the RecordFile. 
*/

  RecordFile rf;   // RecordFile to add the table to
  PageFile   pf;   // PageFile to add the index 
  RecordId   rid;  // record cursor for table scanning
  
  BTreeIndex *tree = new BTreeIndex();

  RC     rc;
  int    key;     
  string value;
  string line;

  // open the table file if exists, otherwise creates file table
  rc = rf.open(table + ".tbl", 'w'); 
  
  // open the index file if exists, otherwise creates file index
  if(index){
	rc = tree->open(table + ".idx", 'w'); 
  }
  
  // add to the table file from the beginning
  rid.pid = rid.sid = 0;
  
  // read from the loadfile
  ifstream myfile (loadfile.c_str());
  if(myfile.is_open()){
	while(myfile.good()){
		if(getline (myfile, line)){
			rc = parseLoadLine(line, key, value);
			if ((rc = rf.append(key, value, rid)) < 0){
				// Unable to append the tuple!
				fprintf(stderr, "Error: while reading tuple %d '%s'\n", key, value.c_str());
				goto exit_load;
			}
			if(index){ //  if index is true, for every tuple inserted into the table,
					   //  obtain RecordId of the inserted tuple, and insert a corresponding (key, RecordId) 
					   //  pair to the B+tree of the table.
				if ((rc = tree->insert(key, rid)) < 0){
					// Unable to append the tuple to index!
					fprintf(stderr, "Error: while reading tuple (into index) %d '%s'\n", key, value.c_str());
					goto exit_load;
				}
			}
		}
	}
  }else{
	//unable to open the loadfile! RETURN
	fprintf(stderr, "Error: while reading loadfile(%s) \n", loadfile.c_str());
	goto exit_load;
  }

  rc = 0;

  // close the table file and return
  exit_load:
  tree->close();
  delete tree;
  myfile.close();
  rf.close();
  pf.close();
  return rc;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
